<?php

namespace yifang\db;

use ArrayAccess;
use ArrayIterator;
use Closure;
use Countable;
use DomainException;
use IteratorAggregate;
use JsonSerializable;
use Traversable;
use yifang\db\paginator\driver\Bootstrap;

/**
 * 分页基础类
 */
abstract class Paginator
{
    /**
     * 是否简洁模式
     * @var bool
     */
    protected $simple = false;

    /**
     * 数据集
     */
    protected $items;

    /**
     * 当前页
     * @var int
     */
    protected $currentPage;

    /**
     * 最后一页
     * @var int
     */
    protected $totalPage;

    /**
     * 数据总数
     * @var integer|null
     */
    protected $total;

    /**
     * 每页数量
     * @var int
     */
    protected $listRows;

    /**
     * 是否有下一页
     * @var bool
     */
    protected $hasMore;

    /**
     * 分页配置
     * @var array
     */
    protected $options = [
        'var_page' => 'page',
        'path'     => '/',
        'query'    => [],
        'fragment' => '',
    ];

    /**
     * 获取当前页码
     * @var Closure
     */
    protected static $currentPageResolver;

    /**
     * 获取当前路径
     * @var Closure
     */
    protected static $currentPathResolver;

    /**
     * @var Closure
     */
    protected static $maker;

    public function __construct($items, $listRows, $currentPage = 1, $total = null, $simple = false, array $options = [])
    {
        $this->options = array_merge($this->options, $options);

        $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path'];
        $this->simple   = $simple;
        $this->listRows = $listRows;

        if ($simple) {
            $this->currentPage = $this->setCurrentPage($currentPage);
            $this->hasMore     = count($items) > ($this->listRows);
            $items             = $this->slice($items,0, $this->listRows);
        } else {
            $this->total       = $total;
            $this->totalPage    = (int) ceil($total / $listRows);
            $this->currentPage = $this->setCurrentPage($currentPage);
            $this->hasMore     = $this->currentPage < $this->totalPage;
        }
        $this->items = $items;
        $this->appends(request()->get());
    }

    /**
     * 截取数组
     * @access public
     * @param int  $offset       起始位置
     * @param int  $length       截取长度
     * @param bool $preserveKeys preserveKeys
     * @return static
     */
    public function slice($items,$offset, $length = null, $preserveKeys = false)
    {
        return array_slice($items, $offset, $length, $preserveKeys);
    }

    /**
     * @access public
     * @param mixed $items
     * @param int   $listRows
     * @param int   $currentPage
     * @param int   $total
     * @param bool  $simple
     * @param array $options
     * @return Paginator
     */
    public static function make($items, $listRows, $currentPage = 1, $total = null, $simple = false, array $options = [])
    {
        if (isset(static::$maker)) {
            return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options);
        }
        return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options);
    }

    public static function maker(Closure $resolver)
    {
        static::$maker = $resolver;
    }

    /**
     * 设置当前页
     * @param $currentPage
     * @return int
     */
    protected function setCurrentPage($currentPage)
    {
        if (!$this->simple && $currentPage > $this->totalPage) {
            return $this->totalPage > 0 ? $this->totalPage : 1;
        }
        return $currentPage;
    }

    /**
     * 获取页码对应的链接
     *
     * @access protected
     * @param int $page
     * @return string
     */
    protected function url($page)
    {
        if ($page <= 0) {
            $page = 1;
        }
        if (strpos($this->options['path'], '[PAGE]') === false) {
            $parameters = [$this->options['var_page'] => $page];
            $path       = $this->options['path'];
        } else {
            $parameters = [];
            $path       = str_replace('[PAGE]', (string) $page, $this->options['path']);
        }
        if (count($this->options['query']) > 0) {
            $parameters = array_merge($this->options['query'], $parameters);
        }
        $url = $path;
        if (!empty($parameters)) {
            $url .= '?' . http_build_query($parameters, '', '&');
        }
        return $url . $this->buildFragment();
    }

    /**
     * 构造锚点字符串
     *
     * @access public
     * @return string
     */
    protected function buildFragment()
    {
        return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
    }

    /**
     * 自动获取当前页码
     * @access public
     * @param string $varPage
     * @param int    $default
     * @return int
     */
    public static function getCurrentPage($varPage = 'page',$default = 1)
    {
        if (isset(static::$currentPageResolver)) {
            return call_user_func(static::$currentPageResolver, $varPage);
        }
        return $default;
    }

    /**
     * 渲染分页html
     * @access public
     * @return mixed
     */
    abstract public function render();

    /**
     * 添加URL参数
     *
     * @access public
     * @param array $append
     * @return $this
     */
    public function appends(array $append)
    {
        foreach ($append as $k => $v) {
            if ($k !== $this->options['var_page']) {
                $this->options['query'][$k] = $v;
            }
        }
        return $this;
    }

    /**
     * 设置获取当前路径闭包
     * @param Closure $resolver
     */
    public static function currentPathResolver(Closure $resolver)
    {
        static::$currentPathResolver = $resolver;
    }

    /**
     * 设置获取当前页码闭包
     * @param Closure $resolver
     */
    public static function currentPageResolver(Closure $resolver)
    {
        static::$currentPageResolver = $resolver;
    }

    /**
     * 获取当前页页码
     * @return int
     */
    public function currentPage()
    {
        return $this->currentPage;
    }

    /**
     * 获取数据总条数
     * @return int
     */
    public function total()
    {
        if ($this->simple) {
            throw new DomainException('not support total');
        }
        return $this->total;
    }

    /**
     * 获取每页数量
     * @return int
     */
    public function listRows()
    {
        return $this->listRows;
    }

    /**
     * 获取最后一页页码
     * @return int
     */
    public function totalPage()
    {
        if ($this->simple) {
            throw new DomainException('not support last');
        }
        return $this->totalPage;
    }

    /**
     * 数据是否足够分页
     * @access public
     * @return bool
     */
    public function hasPages()
    {
        return !(1 == $this->currentPage && !$this->hasMore);
    }

    /**
     * 自动获取当前的path
     * @access public
     * @param string $default
     * @return string
     */
    public static function getCurrentPath($default = '/')
    {
        if (isset(static::$currentPathResolver)) {
            return call_user_func(static::$currentPathResolver);
        }
        return $default;
    }

    /**
     * 给每个元素执行个回调
     *
     * @access public
     * @param callable $callback
     * @return $this
     */
    public function each(callable $callback)
    {
        foreach ($this->items as $key => $item) {
            $result = $callback($item, $key);
            if (false === $result) {
                break;
            } elseif (!is_object($item)) {
                $this->items[$key] = $result;
            }
        }
        return $this;
    }

    /**
     * 创建一组分页链接
     *
     * @access public
     * @param int $start
     * @param int $end
     * @return array
     */
    public function getUrlRange($start, $end)
    {
        $urls = [];
        for ($page = $start; $page <= $end; $page++) {
            $urls[$page] = $this->url($page);
        }
        return $urls;
    }

    /**
     * 获取数据集
     */
    public function getItems()
    {
        return $this->items;
    }

    /**
     * 返回数据
     */
    public function data(){
        $data=array();
        $data["total"]=$this->total;
        $data["totalPage"]=$this->totalPage;
        $data["page"]=$this->currentPage;
        $data["hasMore"]=$this->hasMore;
        $data["data"]=$this->items;
        return $data;
    }
}